package com.dl.config.shiro;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.dl.cache.RedisCacheManager;
import com.dl.cache.RedisCacheSessionDao;
import com.dl.config.jwt.JwtTokenManager;
import com.dl.filter.JwtAuthcFilter;
import com.dl.filter.JwtPermsFilter;
import com.dl.filter.JwtRolesFilter;
import com.dl.filter.KickOutUserFilter;
import com.dl.properties.ConfigProperties;
import com.dl.properties.JWTProperties;
import com.dl.properties.ShiroProperties;
import com.dl.utils.ConstantField;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.session.mgt.AbstractSessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.annotation.Resource;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
@EnableConfigurationProperties({ConfigProperties.class, ShiroProperties.class})
@Log4j2
public class ShiroConfig {

    @Resource
    private ConfigProperties configProperties;

    @Resource
    private ShiroProperties shiroProperties;

    @Resource
    private JWTProperties jwtProperties;

    @Resource
    private JwtTokenManager jwtTokenManager;

    /**
     * 构建shiroFilter：负责拦截所有请求
     * 添加shiro的内置过滤器
     * anon： 无需认证就可以访问
     * authc： 必须认证了才能访问
     * user： 已登录或记住我
     * perms： 拥有对某个资源的权限才能访问
     * role： 拥有某个角色权限
     * anon:表示可以匿名使用。 authc:表示需要认证(登录)才能使用，没有参数
     *
     * roles：参数可以写多个，多个时必须加上引号，并且参数之间用逗号分割，当有多个参数时，
     * 例如admins/user/**=roles["admin,guest"],每个参数通过才算通过，相当于hasAllRoles()方法。
     *
     * perms：参数可以写多个，多个时必须加上引号，并且参数之间用逗号分割，
     * 例如/admins/user/**=perms["user:add:*,user:modify:*"]，
     * 当有多个参数时必须每个参数都通过才通过，想当于isPermitedAll()方法。
     *
     * rest：根据请求的方法，相当于/admins/user/**=perms[user:method]
     * ,其中method为post，get，delete等。
     *
     * port：当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,
     * 其中schmal是协议http或https等，serverName是你访问的host,8081是url配置里port的端口，
     * queryString是你访问的url里的？后面的参数。 authcBasic：没有参数表示httpBasic认证
     *
     * ssl:表示安全的url请求，协议为https user:当登入操作时不做检查
     * @param defaultWebSecurityManager
     */
    @Bean
    @Qualifier("securityManager")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        // 设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        Map<String, String> filterMap = new LinkedHashMap<>();

        //授权，正常情况下，没有授权会跳转到为授权页面
        filterMap.put("/user/add","perms[user:add]");
        filterMap.put("/user/update","perms[user:update]");

        filterMap.put("/static/**","anon");//静态资源文件
        filterMap.put("/captcha","anon");//验证码请求
        filterMap.put("/check","anon");//注册页面的检查合法性请求
        filterMap.put("/register","anon");//前往注册页面和注册的请求

        filterMap.put("/test/**","anon");//测试类请求
        filterMap.put("/user/index","user");//首页非敏感操作可以是登录或记住我
        filterMap.put("/avatar/**","authc");//头像文件
        filterMap.put("/upload/**","authc");//上传文件

        bean.setFilters(filterMap());

        filterMap.put("/**","kickOut,authc,jwt-authc");
        bean.setFilterChainDefinitionMap(filterMap);

        //设置登录的请求
        bean.setLoginUrl("/login");
        //登录成功跳转路径
        bean.setSuccessUrl("/user/index");
        //未授权页面
        bean.setUnauthorizedUrl("/unauthorized");
        return bean;
    }

    /**
     * 自定义redis缓存管理器
     */
    @Bean
    public Map<String, Filter> filterMap() {
        //创建一个Map类型的对象存储自定义的sessionFilter(),添加到shiroFilterFactoryBean
        Map<String, Filter> filters = new LinkedHashMap<>();
        filters.put("kickOut", kickOutUserFilter());

        filters.put("jwt-authc", new JwtAuthcFilter(jwtProperties, jwtTokenManager));
        filters.put("jwt-perms", new JwtPermsFilter(jwtProperties, jwtTokenManager));
        filters.put("jwt-roles", new JwtRolesFilter(jwtProperties, jwtTokenManager));

        return filters;
    }

    /**
     * 自定义在线并发登录控制过滤器
     */
    @Bean
    public KickOutUserFilter kickOutUserFilter() {
        KickOutUserFilter kickOutUserFilter = new KickOutUserFilter(sessionManager());
        kickOutUserFilter.setMaxSession(shiroProperties.getMaxSession());//设置同时在线人数，默认为1
        return kickOutUserFilter;
    }

    /**
     * 自定义redis缓存管理器
     */
    @Bean
    public RedisCacheManager cacheManager() {
        return new RedisCacheManager();
    }

    /**
     * 安全管理器：shiro核心组件
     * @param myUserRealm
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm myUserRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 关联userRealm
        securityManager.setRealm(myUserRealm);
        //设置缓存管理器
        //securityManager.setCacheManager(cacheManager());//注释后只有错误的session
        //设置session管理器
        if (configProperties.isRedisEnable()){
            securityManager.setSessionManager(sessionManager());//注释后出现JSESSIONID，并且无session管理
        }else {
            securityManager.setCacheManager(new EhCacheManager());//ehcache实现
        }
        //设置记住我管理器
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }

    /**
     * 自定义realm
     * @param realm
     */
    @Bean
    public UserRealm myUserRealm(@Qualifier("userRealm") UserRealm realm) {
        //UserRealm realm = new UserRealm();
        realm.setName(shiroProperties.getShiroCacheName());
        // 修改凭证密码匹配器，默认是简单
        //HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 自定义密码匹配器，继承HashedCredentialsMatcher，实现重试密码次数限制
        RetryLimitCredentialsMatcher credentialsMatcher = new RetryLimitCredentialsMatcher();
        credentialsMatcher.setRetryLimitNumber(shiroProperties.getRetryLimitNumber());

        //设置加密算法为md5
        credentialsMatcher.setHashAlgorithmName("MD5");
        //设置散列次数
        credentialsMatcher.setHashIterations(1024);
        //设置凭证匹配器
        realm.setCredentialsMatcher(credentialsMatcher);

        //开启缓存管理
        realm.setCacheManager(cacheManager()); //自定义ehcache实现或redis实现
        realm.setCachingEnabled(true); //开启全局缓存
        //realm.setAuthenticationCachingEnabled(true); //显式开启认证缓存，默认为关闭
        realm.setAuthenticationCacheName(shiroProperties.getAuthenticationCacheName());//认证缓存名称
        realm.setAuthorizationCachingEnabled(true); //显式开启授权缓存，默认为开启
        realm.setAuthorizationCacheName(shiroProperties.getAuthorizationCacheName());//授权缓存名称
        return realm;
    }

    @Bean
    public RedisCacheSessionDao sessionDao(){
        //org.apache.shiro.web.util.SavedRequest
        RedisCacheSessionDao sessionDao = new RedisCacheSessionDao();
        sessionDao.setCacheManager(cacheManager());
        sessionDao.setActiveSessionsCacheName(ConstantField.SESSION_PREFIX);
        return sessionDao;
    }

    /**
     * 会话管理器
     */
    @Bean
    public DefaultWebSessionManager sessionManager(){
        //DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        ShiroSessionManager sessionManager = new ShiroSessionManager();
        // 取消首次登录url出现jsessionid
        sessionManager.setSessionIdUrlRewritingEnabled(false);

        // 设置session全局超时时间，默认半小时
        sessionManager.setGlobalSessionTimeout(AbstractSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT);//30分钟
        //sessionManager.setGlobalSessionTimeout(1000 * 60);
        sessionManager.setSessionIdCookieEnabled(true);
        // 设置cookie中session名称
        sessionManager.setSessionIdCookie(sessionIdByCookie());
        // 定时检测session过期，默认一小时检测一次
        sessionManager.setSessionValidationSchedulerEnabled(true);//是否开启Session失效 检测，默认开启
        // 删除过期的session
        sessionManager.setDeleteInvalidSessions(true);
        // 检测扫描信息时间间隔,单位为毫秒
        sessionManager.setSessionValidationInterval(1000 * 60 * 5);//执行敏感操作时也会检测session
        // 设置缓存管理器
        sessionManager.setCacheManager(cacheManager());
        // 设置sessionDao
        sessionManager.setSessionDAO(sessionDao());//注释后全部没有
        return sessionManager;
    }

    /**
     * 保存sessionId的cookie
     */
    @Bean
    public SimpleCookie sessionIdByCookie(){
        log.debug("sessionIdCookieName:{}",shiroProperties.getSessionIdCookieName());
        System.out.println("shiroProperties.getSessionIdCookieName() = " + shiroProperties.getSessionIdCookieName());
        SimpleCookie simpleCookie = new SimpleCookie(shiroProperties.getSessionIdCookieName());
        //只有http的连接才能使用cookie
        simpleCookie.setHttpOnly(true);
        simpleCookie.setPath("/");
        //cookie失效时间，单位秒
        //simpleCookie.setMaxAge(60*60*24);
        return simpleCookie;
    }

    /**
     * 声明记住我管理器和rememberMe的cookie
     */
    @Bean
    public RememberMeManager rememberMeManager(){
        CookieRememberMeManager cookieManage = new CookieRememberMeManager();

        log.debug("rememberMeCookieName:{}",shiroProperties.getRememberMeCookieName());
        SimpleCookie simpleCookie = new SimpleCookie(shiroProperties.getRememberMeCookieName());
        //只有http的连接才能使用cookie
        simpleCookie.setHttpOnly(true);
        simpleCookie.setPath("/");
        //cookie失效时间，单位秒
        simpleCookie.setMaxAge(60*60*24);

        cookieManage.setCookie(simpleCookie);
        cookieManage.setCipherKey(Base64.decode("6ZmI6I2j5Y+R5aSn5ZOlAA=="));
        return cookieManage;
    }

    /**
     * 交由 Spring 来自动地管理 Shiro-Bean 的生命周期
     */
    @Bean
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @DependsOn("lifecycleBeanPostProcessor")
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
        /**
         * 指定强制使用cglib为action创建代理对象
         * setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。
         * 在@Controller注解的类的方法中加入@RequiresRole注解，会导致该方法无法映射请求，导致返回404。
         * 加入这项配置能解决这个bug
         */
        proxyCreator.setUsePrefix(true);
        return proxyCreator;
    }

    // 整合ShiroDialect： 用来整合 Shiro thymeleaf
    @Bean
    public ShiroDialect getShiroDialect() {
        return new ShiroDialect();
    }
}
